﻿using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;
using System.Threading.Tasks;
using FastNet.Internal;

namespace FastNet
{
	[ServiceBehavior( InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple )]
	public sealed class FastServer<TUser> : ICommandChannel, IDisposable
	{
		public FastServer()
		{
			m_host = new ServiceHost( this );
			m_timer = new Timer( this.OnTimerTick, null, Timeout.Infinite, Timeout.Infinite );
		}


		public event EventHandler<FastServerClient<TUser>> ClientConnected;


		public event EventHandler<FastServerClient<TUser>> ClientDisconnected;


		public event EventHandler<MessageEventArgs> ProcessMessage;


		public event EventHandler<ExceptionEventArgs<Exception>> UnhandledException;


		public event EventHandler<ExceptionEventArgs<CommunicationException>> CommunicationException;


		public void AddEndpoint( Binding binding, String address )
		{
			using ( new WriteLock( m_lock ) )
			{
				Logic.Check<ArgumentNullException>( binding != null, "Binding cannot be null" );
				Logic.Check<ArgumentNullException>( address != null, "Address cannot be null" );
				Logic.Check<InvalidOperationException>( m_host.State == CommunicationState.Created, "Cannot add enpoint when the server has already been started" );

				m_host.AddServiceEndpoint( typeof( ICommandChannel ), binding, address );
			}
		}


		public void Start()
		{
			using ( new WriteLock( m_lock ) )
			{
				Logic.Check<InvalidOperationException>( m_host.State == CommunicationState.Created, "This server has already been started" );

				m_host.Open();
				m_timer.Change( INTERVAL_TIMER, Timeout.Infinite );
			}
		}


		public void Dispose()
		{
			Stop( true );
		}


		public void Stop( Boolean terminate )
		{
			using ( new WriteLock( m_lock ) )
			{

			}
		}


		public Boolean Broadcast( FastMessage message )
		{
			Boolean success = false;

			using ( new ReadLock( m_lock ) )
			{
				if ( m_host.State == CommunicationState.Opened )
				{
					success = true;

					foreach ( var client in m_clients.Values )
					{
						client.Send( message );
					}
				}
			}

			return success;
		}


		#region Command Channel Interface


		void ICommandChannel.Open( String username, String password )
		{
			OperationContext		context = OperationContext.Current;
			FastServerClient<TUser> client	= new FastServerClient<TUser>( this, new FastServerClient<TUser>.Credentials( username, password ), context.GetCallbackChannel<ICommandChannel>(), context.SessionId );

			try
			{
				m_lock.EnterReadLock();

				this.ClientConnected.Raise( this, client, m_context );

				Logic.Assert( m_clients.TryAdd( client.Cookie, client ) );
			}
			catch ( FaultException )
			{
				// We will close the connection after the message have been returned to the client.
				OperationContext.Current.OperationCompleted += ( o, e ) =>
				{
					client.Close();
				};

				throw;
			}
			finally
			{
				m_lock.ExitReadLock();
			}
		}


		void ICommandChannel.Send( Byte[] data )
		{
			FastServerClient<TUser>	client = null;

			try
			{
				m_lock.EnterReadLock();

				if ( m_clients.TryGetValue( OperationContext.Current.SessionId, out client ) )
				{
					FastMessage	message = FastMessage.ReadFrom( data );
					
					client.TrackActivity();

					switch ( message.Command )
					{
						case FastCommands.KeepAlive:
							// Nothing is required here.
							break;

						default:
							this.ProcessMessage.RaiseAsync( client, new MessageEventArgs( message ), m_context );
							break;
					}
				}
			}
			catch ( Exception ex )
			{
				this.UnhandledException.RaiseAsync( this, new ExceptionEventArgs<Exception>( ex ), m_context );

				if ( client != null )
				{
					client.Close();
				}
			}
			finally
			{
				m_lock.ExitReadLock();
			}
		}


		#endregion


		internal void DisconnectClient( FastServerClient<TUser> client )
		{
			Logic.Assert( client != null );

			using ( new ReadLock( m_lock ) )
			{
				if ( m_host.State == CommunicationState.Opened )
				{
					if ( m_clients.TryRemove( client.Cookie, out client ) )
					{
						this.ClientDisconnected.RaiseAsync( this, client, m_context );
					}
				}
			}
		}


		internal void RaiseCommunicationException( Object sender, CommunicationException exception )
		{
			this.CommunicationException.RaiseAsync( sender, new ExceptionEventArgs<CommunicationException>( exception ), m_context );
		}


		private void OnTimerTick( Object state )
		{
			FastMessage message = new FastMessage( FastCommands.KeepAlive );
			DateTime	now		= DateTime.UtcNow;

			using ( new ReadLock( m_lock ) )
			{
				foreach ( var client in m_clients.Values.Where( x => x.LastActivityTime.AddMilliseconds( INTERVAL_TIMER ) < now ) )
				{
					client.Send( message );
				}
			}

			m_timer.Change( INTERVAL_TIMER, Timeout.Infinite );
		}


		private Timer													m_timer			= null;
		private ServiceHost												m_host			= null;
		private ReaderWriterLockSlim									m_lock			= new ReaderWriterLockSlim( LockRecursionPolicy.SupportsRecursion );
		private ConcurrentDictionary<String, FastServerClient<TUser>>	m_clients		= new ConcurrentDictionary<String, FastServerClient<TUser>>( StringComparer.Ordinal );
		private SynchronizationContext									m_context		= null;
		private const Int32												INTERVAL_TIMER	= 30 * 1000; // 30 Seconds
	}
}
